package com.qijian.common.utils.file;

import com.github.junrar.Archive;
import com.github.junrar.exception.RarException;
import com.github.junrar.rarfile.FileHeader;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
public final class ZipUtils {

    private ZipUtils() {
    }

    /**
     * 将存放在sourceFilePath目录下的源文件，打包成fileName名称的zip文件，并存放到zipFilePath路径下
     *
     * @param sourceFilePath :待压缩的文件路径
     * @param zipFilePath    :压缩后存放路径
     * @param fileName       :压缩后文件的名称
     * @return
     * @throws IOException
     */
    public static boolean zip(String sourceFilePath, String zipFilePath, String fileName) throws IOException {
        boolean flag = false;
        File sourceFile = new File(sourceFilePath);
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        ZipOutputStream zos = null;

        if (sourceFile.exists() == false) {
            System.out.println("待压缩的文件目录：" + sourceFilePath + "不存在.");
        } else {
            try {
                File zipFile = new File(zipFilePath + fileName + ".zip");
                if (zipFile.exists()) {

                    System.out.println(zipFilePath + "目录下存在名字为:" + fileName + ".zip" + "打包文件.");
                } else {
                    File[] sourceFiles = sourceFile.listFiles();
                    if (null == sourceFiles || sourceFiles.length < 1) {
                        System.out.println("待压缩的文件目录：" + sourceFilePath + "里面不存在文件，无需压缩.");
                    } else {

                        fos = new FileOutputStream(zipFile);
                        zos = new ZipOutputStream(new BufferedOutputStream(fos));
                        byte[] bufs = new byte[1024 * 10];
                        addFile(sourceFile, zos, fis, bis, bufs, zipFilePath.substring(0, zipFilePath.length() - 1));
                        flag = true;
                        if (null != zos) {
                            zos.closeEntry();
                            zos.close();
                        }
                    }
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } finally {
                //关闭流
                try {
                    if (null != fis) {
                        fis.close();
                    }
                    if (null != fos) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
            }
        }
        return flag;
    }

    /**
     * @param zipFile 压缩包文件
     * @param descDir 解压缩文件存放路径
     * @return
     */
    public static boolean unzip(File zipFile, String descDir) {
        try (ZipArchiveInputStream inputStream = getZipFile(zipFile)) {
            File pathFile = new File(descDir);
            if (!pathFile.exists()) {
                pathFile.mkdirs();
            }
            ZipArchiveEntry entry;
            while ((entry = inputStream.getNextZipEntry()) != null) {
                String name = entry.getName();
                if (name.contains("__MACOSX"))
                    continue;
                // 目前不需要按照目录结构创建目录
                if (entry.isDirectory()) {
//                    if (descDir.contains(name))
                    continue;
//                    File directory = new File(descDir, name);
//                    if (!directory.exists())
//                        directory.mkdirs();
                } else {
                    OutputStream os = null;
                    name = name.contains(File.separator) ? name.substring(name.lastIndexOf(File.separator)) : name;
                    try {
                        os = new BufferedOutputStream(new FileOutputStream(new File(descDir, name)));
                        //输出文件路径信息
                        IOUtils.copy(inputStream, os);
                    } finally {
                        IOUtils.closeQuietly(os);
                    }
                }
            }

        } catch (
                Exception e) {
            log.error("[unzip] 解压zip文件出错", e);
            return false;
        }
        return true;
    }

    /**
     * 遍历文件夹，放到zip输出流中去
     *
     * @param source
     * @param zos
     * @param fis
     * @param bis
     * @param bufs
     * @throws IOException
     */
    private static void addFile(File source, ZipOutputStream zos, FileInputStream fis, BufferedInputStream bis, byte[] bufs, String basePath) throws IOException {
        File[] childes = source.listFiles();
        for (File file : childes) {
            if (file.isFile()) {
                //创建ZIP实体，并添加进压缩包
                ZipEntry zipEntry = new ZipEntry(StringUtils.remove(file.getAbsolutePath(), basePath).substring(1));
                zos.putNextEntry(zipEntry);//子文件名称不能相同，会报错

                //读取待压缩的文件并写进压缩包里
                fis = new FileInputStream(file);
                bis = new BufferedInputStream(fis, 1024 * 10);
                int read = 0;
                while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
                    zos.write(bufs, 0, read);
                }
                if (null != bis) {
                    bis.close();
                }
            } else {
                addFile(file, zos, fis, bis, bufs, basePath);
            }
        }
    }


    private static ZipArchiveInputStream getZipFile(File zipFile) throws Exception {
        return new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(zipFile)), getEncoding(zipFile), true);
    }

    /**
     * 迭代删除文件夹
     *
     * @param dirPath 文件夹路径
     */
    public static void deleteDir(String dirPath) {
        File file = new File(dirPath);
        if (file.isFile()) {
            file.delete();
        } else {
            File[] files = file.listFiles();
            if (files == null) {
                file.delete();
            } else {
                for (int i = 0; i < files.length; i++) {
                    deleteDir(files[i].getAbsolutePath());
                }
                file.delete();
            }
        }
    }

    /**
     * 获取文件真实类型
     *
     * @param file 要获取类型的文件。
     * @return 文件类型枚举。
     */
    public static void getFileType(File file, String tempPath) {
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            byte[] head = new byte[4];
            if (-1 == inputStream.read(head)) {
                return;
            }
            int headHex = 0;
            for (byte b : head) {
                headHex <<= 8;
                headHex |= b;
            }
            switch (headHex) {
                case 0x504B0304:
                    unzip(file, tempPath);
                    break;
                case -0x51:
//                    de(file, tempPath);
                    decompress7Z(file, tempPath, true);
                    break;
//                case 0x425a6839:
//                    return FileType.BZ2;
//                case -0x74f7f8:
//                    return FileType.GZ;
                case 0x52617221:
                    decompressRAR(file, tempPath, true);
                    break;
                default:
                    return;
            }
        } catch (RuntimeException re) {
            throw re;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 构建目录
     *
     * @param outputDir 输出目录
     * @param subDir    子目录
     */
    private static void createDirectory(String outputDir, String subDir) {
        File file = new File(outputDir);
        if (!(subDir == null || subDir.trim().equals(""))) {//子目录不为空
            file = new File(outputDir + File.separator + subDir);
        }
        if (!file.exists()) {
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            file.mkdirs();
        }
    }

    /**
     * 解压缩7Zip文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     * @param delete     解压后是否删除原压缩包文件
     */
    public static void decompress7Z(File file, String targetPath, boolean delete) {
        SevenZFile sevenZFile = null;
        OutputStream outputStream = null;
        try {
            sevenZFile = new SevenZFile(file);
            // 创建输出目录
            createDirectory(targetPath, null);
            SevenZArchiveEntry entry;

            while ((entry = sevenZFile.getNextEntry()) != null) {
                if (entry.isDirectory()) {
                    createDirectory(targetPath, entry.getName()); // 创建子目录
                } else {
                    outputStream = new FileOutputStream(new File(targetPath + File.separator + entry.getName()));
                    int len = 0;
                    byte[] b = new byte[2048];
                    while ((len = sevenZFile.read(b)) != -1) {
                        outputStream.write(b, 0, len);
                    }
                    outputStream.flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (sevenZFile != null) {
                    sevenZFile.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 解压缩RAR文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     * @param delete     解压后是否删除原压缩包文件
     */
    private static void decompressRAR(File file, String targetPath, boolean delete) {
        Archive archive = null;
        OutputStream outputStream = null;
        try {
            archive = new Archive(file);
            if (archive.getFileHeaders().size() == 0) {
                throw new RuntimeException("无法解析该rar文件，压缩方式可能是rar5或压缩包为空，建议使用zip");
            }
            FileHeader fileHeader;
            // 创建输出目录
            createDirectory(targetPath, null);
            while ((fileHeader = archive.nextFileHeader()) != null) {
                if (fileHeader.getFileNameString().contains("__MACOSX")) {
                    continue;
                }
                if (fileHeader.isDirectory()) {
                    createDirectory(targetPath, fileHeader.getFileNameString().trim()); // 创建子目录
                } else {
                    outputStream = new FileOutputStream(new File(targetPath + File.separator + fileHeader.getFileNameString().trim()));
                    archive.extractFile(fileHeader, outputStream);
                }
            }
        } catch (RarException | IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (archive != null) {
                    archive.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @SuppressWarnings("unchecked")
    private static String getEncoding(File zipF) throws Exception {
        String encoding = "UTF-8";
        net.lingala.zip4j.core.ZipFile zipFile = new net.lingala.zip4j.core.ZipFile(zipF);
        zipFile.setFileNameCharset(encoding);
        List<net.lingala.zip4j.model.FileHeader> list = zipFile.getFileHeaders();
        for (int i = 0; i < list.size(); i++) {
            net.lingala.zip4j.model.FileHeader fileHeader = list.get(i);
            String fileName = fileHeader.getFileName();
            if (isMessyCode(fileName)) {
                encoding = "GBK";
                break;
            }
        }
        return encoding;
    }

    private static boolean isMessyCode(String str) {
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if ((int) c == 0xfffd) return true;
        }
        return false;
    }
}